/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.editpart;

import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.commands.UnexecutableCommand;
import org.eclipse.gef.editpolicies.ResizableEditPolicy;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.uml2.uml.Element;

import com.cea.papyrus.core.commands.AddUMLElementToDiagram;
import com.cea.papyrus.core.commands.Di2AddCommand;
import com.cea.papyrus.core.commands.Di2UMLElementCreateCommand;
import com.cea.papyrus.core.commands.Di2WithoutUMLAddCommand;
import com.cea.papyrus.core.commands.GraphNodeSetConstraintCommand;
import com.cea.papyrus.core.commands.UMLElementAddCommand;
import com.cea.papyrus.core.commands.factories.UMLDi2ElementCreateFactory;
import com.cea.papyrus.core.dnd.ObjectTransfer;
import com.cea.papyrus.core.figures.MiddleEndpointLocator;
import com.cea.papyrus.core.figures.RelationEndpointLocator;
import com.cea.papyrus.core.pseudoelement.EnhancedType;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.DiagramElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;

/**
 * 
 * 
 * @author Patrick Tessier
 * @since 5 sept. 06
 * 
 * this class is used to describe the policy of a diagram
 * How to add an element...
 */

public class LayoutEditPolicy
extends org.eclipse.gef.editpolicies.XYLayoutEditPolicy
{
	
	/**
	 * 
	 */
	protected final static int channel=13;

	/**
	 * this is the constructor.
	 * 
	 * @param layout the layout of the diagram
	 */
	public LayoutEditPolicy(XYLayout layout) {
		super();
		this.setXyLayout(layout);
	}

	/**
	 * used to create an add command.
	 * 
	 * @param childEditPart the editpart that is conecerned
	 * @param constraint to set on this editpart
	 * @param request to create an add command
	 * 
	 * @return the addcommand
	 */

	protected Command createAddCommand(ChangeBoundsRequest request, EditPart childEditPart, 
			Object constraint) {

		GraphNode childNode = (GraphNode)childEditPart.getModel();
		GraphElement parentGE=(GraphElement)this.getHost().getModel();
		Element umlParent=null;
		Element umlChild=null;
		try{
			umlParent=LookForElement.lookForUMLElementOf(parentGE);
			umlChild=LookForElement.lookForUMLElementOf(childNode);
		}
		catch(Exception e){
			Debug.log(e);
		}
		CompoundCommand cmd = new CompoundCommand();
		GraphNodeSetConstraintCommand locationCommand = new GraphNodeSetConstraintCommand(childNode, request, (Rectangle)constraint);
		cmd.add(locationCommand);
		
//		// modify for element imports
//		if(childEditPart instanceof IUMLElementEditPart ) {
//			if(((IUMLElementEditPart)childEditPart).isElementImport()) {
//				// retrieve element import in the model
//				ElementImport ei = ((IUMLElementEditPart)childEditPart).getElementImport();
//				
//			}
//		}
		
		// graphElements that represent label don't have to move another graphElement
		if( (!(childEditPart instanceof LinkNameDisplayEditPart)) 
			&& (!(childEditPart instanceof RelationEndDisplayEditPart))
			&& (!(childEditPart instanceof NodeNameDisplayEditPart)) ){
			UMLElementAddCommand umlAdd= new UMLElementAddCommand(umlParent,umlChild);
			cmd.add(umlAdd);
			
			
			// RS: bug NPE when childNode has no semanticModel, check before creating the command, should 
			// return an unexecutable command instead.
			if(childNode.getSemanticModel()!=null) {
				Di2AddCommand di2add =new Di2AddCommand(childNode, parentGE, getCurrentTypeDiagram());
				// whe have to precise
				// the helper take in account the semantic parent not the real graphNode Parent
				if( childNode.getSemanticParent()!=null ){
					di2add.setOldParent((GraphElement)childNode.getSemanticParent());
				}
				cmd.add(di2add);
			} else {
				// command should not be executable
				cmd.add(UnexecutableCommand.INSTANCE);
			}
		}
		Debug.debug(this,"End of adding a SetConstraintCommand ", channel);
		return cmd;
	}

	/**
	 * 
	 * 
	 * @param constraint 
	 * @param request 
	 * @param graphNode 
	 * 
	 * @return 
	 */
	protected Command createLabelAddCommand(ChangeBoundsRequest request,GraphNode graphNode,Object constraint) {

		CompoundCommand cmd = new CompoundCommand();
		/*GraphNodeSetConstraintCommand locationCommand = new GraphNodeSetConstraintCommand(graphNode, request, (Rectangle)constraint);
		cmd.add(locationCommand);*/
		// graphElements that represent label don't have to move another graphElement
		GraphElement parentGE=(GraphElement)this.getHost().getModel();
		Point absoluteCoordiante=LookForElement.calculateAbsolutePosition(graphNode, new Point(0,0));
		GraphNodeSetConstraintCommand locationCommand = new GraphNodeSetConstraintCommand(graphNode, request, new Rectangle(
				absoluteCoordiante.x-parentGE.getPosition().x+request.getMoveDelta().x,
				absoluteCoordiante.y-parentGE.getPosition().y+request.getMoveDelta().y,
				graphNode.getSize().width,
				graphNode.getSize().height));
		cmd.add(locationCommand);

		Di2WithoutUMLAddCommand di2add =new Di2WithoutUMLAddCommand(graphNode, parentGE, getCurrentTypeDiagram());
		cmd.add(di2add);
		return cmd;
	}
	
	/**
	 * 
	 * 
	 * @param child 
	 * @param constraint 
	 * 
	 * @return 
	 * 
	 * @see org.eclipse.gef.editpolicies.ConstrainedLayoutEditPolicy#createChangeConstraintCommand(org.eclipse.gef.EditPart, java.lang.Object)
	 */
	@Override
	protected Command createChangeConstraintCommand(EditPart child, Object constraint) {
		return null;
	}
	
	/**
	 * 
	 * 
	 * @param child 
	 * @param constraint 
	 * @param request 
	 * 
	 * @return 
	 * 
	 * @see org.eclipse.gef.editpolicies.ConstrainedLayoutEditPolicy#createChangeConstraintCommand(org.eclipse.gef.requests.ChangeBoundsRequest, org.eclipse.gef.EditPart, java.lang.Object)
	 */
	@Override	
	protected Command createChangeConstraintCommand(ChangeBoundsRequest request, 
			EditPart child, Object constraint) {
		CompoundCommand command= new CompoundCommand();

		command.add(new GraphNodeSetConstraintCommand((GraphNode)child.getModel(), request, (Rectangle)constraint));
		GraphElement  ge= (GraphElement)child.getModel();
		Iterator itSemanticChild=ge.getSemanticChild().iterator();
		Dimension diff=((Rectangle)constraint).getLocation().getDifference(((GraphNode)child.getModel()).getPosition());
		
		//look SemanticChildren  connected to graph element
		while (itSemanticChild.hasNext()){
			
			GraphElement geSChild=(GraphElement)itSemanticChild.next();
			
			// If semantic child (geSChild) is a graphNode, propagate move 
			// else (edge) do nothing (will follow automatically)
			
			if( geSChild instanceof GraphNode){				
				// Create a move command for this element
				Rectangle rec =new Rectangle(geSChild.getPosition(),((GraphNode)geSChild).getSize());
				rec=rec.getTranslated(diff.width,diff.height);
	
				Command locationCommand = new GraphNodeSetConstraintCommand((GraphNode)geSChild, request, rec);
				// add this command in the list of commands
				command.add(locationCommand);
			}
		}


		return command;
	}
	/*
	 * (non-Javadoc)
	 * @see org.eclipse.gef.editpolicies.ConstrainedLayoutEditPolicy#createChildEditPolicy(org.eclipse.gef.EditPart)
	 */
	/**
	 * 
	 * 
	 * @param child 
	 * 
	 * @return 
	 */
	@Override
	protected EditPolicy createChildEditPolicy(EditPart child) {
		if (child instanceof GraphNodeEditPart ) {
			ResizableEditPolicy policy = new ElementResizableEditPolicy();
//			policy.setResizeDirections(1);
			return policy;}
		return super.createChildEditPolicy(child);
	}


	/**
	 * 
	 * 
	 * @param generic 
	 * 
	 * @return 
	 */
	@Override
	protected Command getAddCommand(Request generic) {
		Debug.debug(this,"getAddCommand ",channel);
		ChangeBoundsRequest request = (ChangeBoundsRequest)generic;
		List editParts = request.getEditParts();
		CompoundCommand command = new CompoundCommand();
		command.setDebugLabel("Add in ConstrainedLayoutEditPolicy");//$NON-NLS-1$
		GraphNodeEditPart childPart;
		Rectangle r;
		Object constraint;

		for (int i = 0; i < editParts.size(); i++) {
			childPart = (GraphNodeEditPart)editParts.get(i);
			Debug.debug(this,"childPart "+childPart, channel);
			r = childPart.getFigure().getBounds().getCopy();
			Debug.debug(this,"position child :"+ r.getLocation(), channel);


			//calculate new Position
			/// case of nameDisplay
			// the position of a port is alway absolute
			if ( childPart.needRelativeTranslationForAddCommand()) {
//			if(        (!(childPart instanceof LinkNameDisplayEditPart))
//				&&(!(childPart instanceof RelationEndDisplayEditPart))
//				&&(!(childPart instanceof PortEditPart))
//				&&(!(childPart instanceof TemplateSignatureEditPart))
//				&&(!(childPart instanceof ActivityParameterNodeEditPart))
//				&&(!(childPart instanceof OutputPinEditPart))
//				&&(!(childPart instanceof InputPinEditPart))
//				&&(!(childPart instanceof NodeNameDisplayEditPart))
//				&&(!(childPart instanceof ConnectionPointReferenceEditPart))
//				&&(!((childPart instanceof PseudostateEditPart) && ((PseudostateEditPart)childPart).isEntryOrExit()))) {
				//convert r to absolute from childpart figure
				childPart.getFigure().translateToAbsolute(r);
				Debug.debug(this,"position child absolute:"+ r.getLocation(), channel);
				r = request.getTransformedRectangle(r);
				Debug.debug(this,"position child with move:"+ r.getLocation(), channel);

				//convert this figure to relative 

				Debug.debug(this,"Translate to relative!!!", channel);
				this.getLayoutContainer().translateToRelative(r);
				this.getLayoutContainer().translateFromParent(r);
			}
			else {
				//othe case
				r = request.getTransformedRectangle(r);
				Debug.debug(this,"position child with move:"+ r.getLocation(), channel);
			}

			Debug.debug(this,"position child in new element:"+ r.getLocation(), channel);
			r.translate(this.getLayoutOrigin().getNegated());

			if (childPart instanceof RelationEndDisplayEditPart ) {

				RelationEndDisplayEditPart theRoleLabel=(RelationEndDisplayEditPart) childPart;

				//position of the connector 
				RelationEndpointLocator  lo =new RelationEndpointLocator ((PolylineConnection )((GraphEdgeEditPart)theRoleLabel.getParent()).getFigure(),
						theRoleLabel.isAtEnd(),
						new Point(0,0));
				Point absolute =r.getLocation();
				Point midConnection=lo.getLocation();
				r.setLocation(new Point(r.x-midConnection.x,
						r.y-midConnection.y));

			}

			else if (childPart instanceof ExtensionRequiredEditPart){

				ExtensionRequiredEditPart theRoleLabel=(ExtensionRequiredEditPart) childPart;

				//position of the connector 
				RelationEndpointLocator  lo =new RelationEndpointLocator ((PolylineConnection )((GraphEdgeEditPart)theRoleLabel.getParent()).getFigure(),
						false,
						new Point(0,0));
				Point absolute =r.getLocation();
				Point midConnection=lo.getLocation();
				r.setLocation(new Point(r.x-midConnection.x,
						r.y-midConnection.y));

			}
			
			else if (childPart instanceof LinkNameDisplayEditPart){
				LinkNameDisplayEditPart theNameLabel=(LinkNameDisplayEditPart) childPart;
				//position du milieu du label

				org.eclipse.draw2d.geometry.Point middleLabel =new org.eclipse.draw2d.geometry.Point(
						((IFigure) theNameLabel.getFigure()).getPreferredSize().width/2,
						((IFigure) theNameLabel.getFigure()).getPreferredSize().height/2
				);

				//position of the connector 
				MiddleEndpointLocator lo =new MiddleEndpointLocator ((PolylineConnection )((GraphEdgeEditPart)theNameLabel.getParent()).getFigure(),
						new Point(0,0));
				Point absolute =r.getLocation();
				Point midConnection=lo.getLocation();
				r.setLocation(new Point(r.x-midConnection.x+middleLabel.x,r.y-midConnection.y+middleLabel.y));

			}

			else if (childPart instanceof NodeNameDisplayEditPart) {
				constraint = this.getConstraintFor(r);
				GraphNodeSetConstraintCommand locationCommand = new GraphNodeSetConstraintCommand(((GraphNode)childPart.getModel()), request, (Rectangle)constraint);
				return locationCommand;
			}
			
			

			Debug.debug(this,"Command is contructing ", channel);
			constraint = this.getConstraintFor(r);
			
			command.add(createAddCommand(request, childPart, translateToModelConstraint(constraint)));
			if(childPart.getModel() instanceof GraphNode){
				Iterator<DiagramElement> itSemanticChild= ((GraphNode)childPart.getModel()).getSemanticChild().iterator();

				while(itSemanticChild.hasNext()){
					GraphNode graphNode=(GraphNode)itSemanticChild.next();
					//move Label
					if( graphNode.getSemanticModel()==null){
						command.add( createLabelAddCommand(request, graphNode, constraint));
					}
				}
			}

		}
		Command c= command.unwrap();
		return c;
	}


	/**
	 * Override to return the <code>Command</code> to perform an {@link
	 * RequestConstants#REQ_CLONE CLONE}. By default, <code>null</code> is
	 * returned.
	 * 
	 * @param request the Clone Request
	 * 
	 * @return A command to perform the Clone.
	 */
	/*
protected Command getCloneCommand(ChangeBoundsRequest request) {
	CloneCommand clone = new CloneCommand();

	clone.setParent((LogicDiagram)getHost().getModel());

	Iterator i = request.getEditParts().iterator();
	GraphicalEditPart currPart = null;

	while (i.hasNext()) {
		currPart = (GraphicalEditPart)i.next();
		clone.addPart((LogicSubpart)currPart.getModel(), 
				(Rectangle)getConstraintForClone(currPart, request));
	}

	// Attach to horizontal guide, if one is given
	Integer guidePos = (Integer)request.getExtendedData()
			.get(SnapToGuides.KEY_HORIZONTAL_GUIDE);
	if (guidePos != null) {
		int hAlignment = ((Integer)request.getExtendedData()
				.get(SnapToGuides.KEY_HORIZONTAL_ANCHOR)).intValue();
		clone.setGuide(findGuideAt(guidePos.intValue(), true), hAlignment, true);
	}

	// Attach to vertical guide, if one is given
	guidePos = (Integer)request.getExtendedData()
			.get(SnapToGuides.KEY_VERTICAL_GUIDE);
	if (guidePos != null) {
		int vAlignment = ((Integer)request.getExtendedData()
				.get(SnapToGuides.KEY_VERTICAL_ANCHOR)).intValue();
		clone.setGuide(findGuideAt(guidePos.intValue(), false), vAlignment, false);
	}

	return clone;
}
	 */

	@Override
	protected Command getCreateCommand(CreateRequest request) {
		
		// this is a drag'n'drop
		if (request.getExtendedData().get(ObjectTransfer.UML_TRANSFER_OBJECT)!=null){
			CompoundCommand command= new CompoundCommand();
			List elementList= (List)request.getExtendedData().get(ObjectTransfer.UML_TRANSFER_OBJECT);
			Iterator it= elementList.iterator();
			int i =0;
			while (it.hasNext()){
				Object o = it.next();
				//Darpan
				if (!(o instanceof Element)) {
					return null;
				}
				//Darpan
				Element elt= (Element) o;
				GraphElement parent=(GraphElement)this.getHost().getModel();
				Point location= request.getLocation().getTranslated(i*120, 0);
				this.getLayoutContainer().translateToRelative(location);
				this.getLayoutContainer().translateFromParent(location);
				location.translate(this.getLayoutOrigin().getNegated());
				AddUMLElementToDiagram addIntoDiagram =new AddUMLElementToDiagram(elt,parent,location,null);
				i++;
				command.add(addIntoDiagram);
			}
			command.setLabel("Add Into Diagram");
			return command;
		}

		// this is a simple creation request
		Object	newObjectDescription = EnhancedType.getNewObjectDescription(request);

		Di2UMLElementCreateCommand	createCommand = null;
		Point p;
		Debug.debug(this,"getCreateCommand:"+ newObjectDescription, channel);

		createCommand = getCreateCommandFactory().createCommand(newObjectDescription, (GraphElement)this.getHost().getModel(), getCurrentTypeDiagram());
		createCommand.setCreateRequest(request);
		p= request.getLocation();

		// If accelerator is used, the position is null
		if (p!=null){
			//the element creation is done withe a position 
			this.getLayoutContainer().translateToRelative(p);
			this.getLayoutContainer().translateFromParent(p);
			p.translate(this.getLayoutOrigin().getNegated());
			createCommand.setLocation(p);
		}

		if( request.getSize()!=null){
			createCommand.setSize(request.getSize());
		}
		createCommand.setLabel("Create Element");
		return createCommand;
	}

    /**
     * Get the create command factory.
     * This method can be overriden by subclasses in order to return a specific factory.
     * @return
     */
    protected UMLDi2ElementCreateFactory getCreateCommandFactory() {
        return UMLDi2ElementCreateFactory.eINSTANCE;
    }
    
	/*
	 * (non-Javadoc)
	 * @see org.eclipse.gef.editpolicies.LayoutEditPolicy#getOrphanChildrenCommand(org.eclipse.gef.Request)
	 */
	/**
	 * 
	 * 
	 * @param request 
	 * 
	 * @return 
	 */
	@Override
	protected Command getOrphanChildrenCommand(Request request) {
		return null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.gef.editpolicies.LayoutEditPolicy#getCreationFeedbackOffset(org.eclipse.gef.requests.CreateRequest)

protected Insets getCreationFeedbackOffset(CreateRequest request) {
	if (request.getNewObject() instanceof LED 
			|| request.getNewObject() instanceof Circuit)
		return new Insets(2, 0, 2, 0);
	return new Insets();
}
	 */

	/**
	 * to display the content of a compoundCommand.
	 * 
	 * @param command the command to display
	 */
	protected void displayCompoundCommand(CompoundCommand command) {
		Iterator<Command> it = command.getCommands().iterator();
		while(it.hasNext()) {
			this.displayCommand(it.next());
		}
	}
	
	/**
	 * display the content of command.
	 * 
	 * @param command to display
	 */
	protected void displayCommand(Command command) {
		Debug.debug(this,"DisplayCommand "+command.toString()+"!!", 21);
		if(command instanceof CompoundCommand) {
			this.displayCompoundCommand((CompoundCommand)command);
		}

		else {
			Debug.debug(this, command.toString() + command.canExecute(), 21);
		}
	}

	/**
	 * Returns the current diagram type for this edit policy.
	 * 
	 * @return the current diagram type for this edit policy
	 */
	public String getCurrentTypeDiagram() {
		//Darpan
		if (this.getHost().getRoot().getContents() instanceof DiagramEditPart) {
			return ((DiagramEditPart)this.getHost().getRoot().getContents()).getDiagram().getType();
		} else {
			EditPart parent = this.getHost().getParent();
			int i = 0; // dont want a stackoverflow here
			while (!(parent.getModel() instanceof Diagram) && i < 1000) {
				parent = parent.getParent();
				++i;
			}
			return ((Diagram) parent.getModel()).getType();
		}
		//Darpan
		//return ((DiagramEditPart)this.getHost().getRoot().getContents()).getDiagram().getType();
	}
}